//
// Created by feng on 19-3-31.
// PS: Define the rectangle receiver data structure.
//

/**
 * 由于输入文件中只有receiver的size和pos，所以需要设置surface_index，标注接收器的平面是对着什么方向的
 * 同时要设置receiver的四个顶点和local normal
 */

#include "CudaRectangleReceiver.cuh"
#include <algorithm/shared/utils/GenericVectorMath.h>
#include <algorithm/rayTracing/qmcrt_util/vector_arithmetic.cuh>


#include <stdexcept>

using namespace solar::cuda;

CudaRectangleReceiver::CudaRectangleReceiver(const CudaRectangleReceiver& rectangle_receiver)
    : CudaReceiver(rectangle_receiver)
{
    //        for(int i = 0; i < 4; ++i){
    //            rect_vertexes_[i] = rectangle_receiver.getRectangleVertex(i);
    //        }
    rect_vertexes_[0] = rectangle_receiver.getRectangleVertex(0);
    rect_vertexes_[1] = rectangle_receiver.getRectangleVertex(1);
    rect_vertexes_[2] = rectangle_receiver.getRectangleVertex(2);
    rect_vertexes_[3] = rectangle_receiver.getRectangleVertex(3);
    local_normal_ = rectangle_receiver.getLocalNormal();
}

void CudaRectangleReceiver::CInit(float pixel_per_meter_for_receiver)
{
    pixel_length_ = 1.0f / float(pixel_per_meter_for_receiver);
    Cinit_vertex();
    setFocusCenter();
    Cset_resolution(pixel_per_meter_for_receiver);
    Calloc_image(); // cudaMalloc according to resolution. size = resulotion.x * resolution.y
    Cclean_image_content();

    // printf("---------rev vertexts--------------\n");
    // for (int i = 0; i < 4; i++)
    // {
    //     printf("vertex[%d]: %f %f %f\n", i, rect_vertexes_[i].x, rect_vertexes_[i].y,
    //            rect_vertexes_[i].z);
    // }
    // printf("--------------------------------\n");
}

auto CudaRectangleReceiver::getRectangleVertex(int index) const -> float3
{
    if (index > 3)
    {
        throw std::runtime_error("The index should between [0, 3].");
    }
    return rect_vertexes_[index];
}

auto CudaRectangleReceiver::getLocalNormal() const -> float3 { return local_normal_; }

auto CudaRectangleReceiver::getFocusCenter(const float3& heliostat_position) -> float3
{
    return focus_center_;
}

void CudaRectangleReceiver::Cinit_vertex()
{
    Cset_local_normal(); // Set local normal
    Cset_local_vertex(); // Set local vertex according to face type
    Cset_world_vertex(); // Set world vertex according to normal
}

void CudaRectangleReceiver::asImagePlane(const float3& new_x_axis)
{
    Cset_local_normal(); // Set local normal
    Cset_local_vertex(); // Set local vertex according to face type
    setWorldVertexAsImagePlane(new_x_axis); // Set world vertex according to normal
}

// 即receiver的平面是朝向哪里的，总共6个方向
void CudaRectangleReceiver::Cset_local_normal()
{
    switch (surface_index_)
    {
    case 0:
        local_normal_ = make_float3(0.0f, 0.0f, 1.0f);
        break; // 法向沿着z轴正向
    case 1:
        local_normal_ = make_float3(1.0f, 0.0f, 0.0f);
        break;
    case 2:
        local_normal_ = make_float3(0.0f, 0.0f, -1.0f);
        break;
    case 3:
        local_normal_ = make_float3(-1.0f, 0.0f, 0.0f);
        break;
    case 4:
        local_normal_ = make_float3(0.0f, 1.0f, 0.0f);
        break;
    case 5:
        local_normal_ = make_float3(0.0f, -1.0f, 0.0f);
        break;
    default:
        break;
    }
}

// 根据receiver不同的朝向，写出接收器的四个顶点的局部坐标(局部坐标系的原点是接收器中心)
void CudaRectangleReceiver::Cset_local_vertex()
{
    switch (surface_index_)
    {
    case 0:
        /*
            接收器的四个顶点：
                    vertex[1]  ————————————  vertex[2]
                              |           |
                              |           |
                              |           |
                    vertex[0]  ————————————  vertex[3]
        */
        rect_vertexes_[0] = make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[1] = make_float3(-size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[2] = make_float3(size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[3] = make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        break;
    case 1:
        rect_vertexes_[0] = make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[1] = make_float3(size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[2] = make_float3(size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[3] = make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    case 2:
        rect_vertexes_[0] = make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertexes_[1] = make_float3(size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[2] = make_float3(-size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[3] = make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    case 3:
        rect_vertexes_[0] = make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertexes_[1] = make_float3(-size_.x / 2, size_.y / 2, -size_.z / 2);
        rect_vertexes_[2] = make_float3(-size_.x / 2, size_.y / 2, size_.z / 2);
        rect_vertexes_[3] = make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        break;
    case 5:
        rect_vertexes_[0] = make_float3(-size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[1] = make_float3(size_.x / 2, -size_.y / 2, size_.z / 2);
        rect_vertexes_[2] = make_float3(size_.x / 2, -size_.y / 2, -size_.z / 2);
        rect_vertexes_[3] = make_float3(-size_.x / 2, -size_.y / 2, -size_.z / 2);
        break;
    default:
        break;
    }
}

void CudaRectangleReceiver::Cset_world_vertex()
{
    normal_ = normalize(normal_);
    //        for(float3 vertex : rect_vertexes_){
    //            vertex = global_func::rotateY(vertex, local_normal_, normal_);
    //            vertex = global_func::translate(vertex, pos_);
    //        }

    // printf("--------------local_normal vs. normal----------------\n");
    // printf("\tlocal_normal = %f %f %f \n", local_normal_.x, local_normal_.y, local_normal_.z);
    // printf("\tnormal = %f %f %f\n", normal_.x, normal_.y, normal_.z);
    // printf("-----------------------------------------------------\n");

    rect_vertexes_[0] = global_func::rotateY(rect_vertexes_[0], local_normal_, normal_);
    rect_vertexes_[1] = global_func::rotateY(rect_vertexes_[1], local_normal_, normal_);
    rect_vertexes_[2] = global_func::rotateY(rect_vertexes_[2], local_normal_, normal_);
    rect_vertexes_[3] = global_func::rotateY(rect_vertexes_[3], local_normal_, normal_);

    rect_vertexes_[0] = global_func::translate(rect_vertexes_[0], pos_);
    rect_vertexes_[1] = global_func::translate(rect_vertexes_[1], pos_);
    rect_vertexes_[2] = global_func::translate(rect_vertexes_[2], pos_);
    rect_vertexes_[3] = global_func::translate(rect_vertexes_[3], pos_);

    // printf("%f %f %f\n",rect_vertexes_[0].x,rect_vertexes_[0].y,rect_vertexes_[0].z);
    // printf("%f %f %f\n",rect_vertexes_[1].x,rect_vertexes_[1].y,rect_vertexes_[1].z);
    // printf("%f %f %f\n",rect_vertexes_[2].x,rect_vertexes_[2].y,rect_vertexes_[2].z);
    // printf("%f %f %f\n",rect_vertexes_[3].x,rect_vertexes_[3].y,rect_vertexes_[3].z);
}

void CudaRectangleReceiver::setWorldVertexAsImagePlane(const float3& new_x_axis)
{
    // Copied from CudaRectangleReceiver::Cset_world_vertex()

    normal_ = normalize(normal_);
    rect_vertexes_[0] = global_func::rotateY(rect_vertexes_[0], local_normal_, normal_);
    rect_vertexes_[1] = global_func::rotateY(rect_vertexes_[1], local_normal_, normal_);
    rect_vertexes_[2] = global_func::rotateY(rect_vertexes_[2], local_normal_, normal_);
    rect_vertexes_[3] = global_func::rotateY(rect_vertexes_[3], local_normal_, normal_);

    float3 old_x_axis = {1, 0, 0};
    auto real_new_x_axis = normalize(new_x_axis);
    if (real_new_x_axis.x < 0)
    {
        real_new_x_axis = solar::internal::vector::negate(real_new_x_axis);
    }
    rect_vertexes_[0] = global_func::rotateY(rect_vertexes_[0], old_x_axis, real_new_x_axis);
    rect_vertexes_[1] = global_func::rotateY(rect_vertexes_[1], old_x_axis, real_new_x_axis);
    rect_vertexes_[2] = global_func::rotateY(rect_vertexes_[2], old_x_axis, real_new_x_axis);
    rect_vertexes_[3] = global_func::rotateY(rect_vertexes_[3], old_x_axis, real_new_x_axis);


    rect_vertexes_[0] = global_func::translate(rect_vertexes_[0], pos_);
    rect_vertexes_[1] = global_func::translate(rect_vertexes_[1], pos_);
    rect_vertexes_[2] = global_func::translate(rect_vertexes_[2], pos_);
    rect_vertexes_[3] = global_func::translate(rect_vertexes_[3], pos_);
}

void CudaRectangleReceiver::Cset_resolution(float pixel_per_meter_for_receiver)
{
    switch (surface_index_)
    {
    case 0:
        resolution_.x = size_.x * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.y * float(pixel_per_meter_for_receiver);
        // printf("%f %f %f %d
        // %d\n",size_.x,size_.y,float(pixel_per_meter_for_receiver),resolution_.x,resolution_.y);
        break;
    case 1:
        resolution_.x = size_.z * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.y * float(pixel_per_meter_for_receiver);
        // printf("       resolution %d %d \n",resolution_.x,resolution_.y);
        break;
    case 2:
        resolution_.x = size_.x * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.y * float(pixel_per_meter_for_receiver);
        break;
    case 3:
        resolution_.x = size_.z * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.y * float(pixel_per_meter_for_receiver);
        break;
    case 4:
        resolution_.x = size_.x * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.z * float(pixel_per_meter_for_receiver);
        break;
    case 5:
        resolution_.x = size_.x * float(pixel_per_meter_for_receiver);
        resolution_.y = size_.z * float(pixel_per_meter_for_receiver);
        break;
    default:
        break;
    }
    // printf("size= %f %f %f\n",size_.x,size_.y,size_.z);
    // printf("surface_index %d resolution %d %d \n",surface_index_,resolution_.x,resolution_.y);
    // resolution_.x*=float(pixel_per_meter_for_receiver);
    // resolution_.y *= float(pixel_per_meter_for_receiver);
}

void CudaRectangleReceiver::setFocusCenter()
{
    // float3 err={0.5,0.0,0.0};
    focus_center_ = (rect_vertexes_[0] + rect_vertexes_[2]) / 2;
    // printf("接收器中心  %f %f %f\n", focus_center_.x, focus_center_.y, focus_center_.z);
}
